#ifndef SSY_MATRIX_TRIANGULAR_H
#define SSY_MATRIX_TRIANGULAR_H


#include "MutrixBase.h"

template<typename ElementType>
class MatrixTriangular : public MatrixBase<ElementType> {
public:
	MatrixTriangular(size_t series, enum matrix_type type = MATRIX_TRIANGULAR_DEFAULT) 
		: MatrixBase(series, series) {
		_type = type;
		_mut.resize(GetRow());
		switch (_type) {
			case MATRIX_TRIANGULAR_LOWER:
				for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) 
					_mut[rIdx].resize(rIdx+1);
				break;
			case MATRIX_TRIANGULAR_UPPER:
				for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) 
					_mut[rIdx].resize(GetRow()-rIdx);
				break;
			default:
				break;
		}
	}
	explicit MatrixTriangular(const MatrixBase& matbase);
	~MatrixTriangular(void) {
		for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
			vector<ElementType>().swap(_mut[rIdx]);
		}
		vector<vector<ElementType>>().swap(_mut);
	}

	const ElementType& GetValue(size_t rowidx, size_t colidx) const;
	bool SetValue(size_t rowidx, size_t colidx, const ElementType& element);
	void Print(void) const;

	MatrixTriangular<ElementType> Transpose(void) const;
	MatrixTriangular<ElementType>& operator=(const MatrixBase<ElementType>& matbase);
	const vector<ElementType>& operator[](size_t row) const { return _mut[row]; } 
	vector<ElementType>& operator[](size_t row) { 
		return const_cast<vector<ElementType>&>
			(static_cast<const MatrixTriangular<ElementType>&>(*this)[row]); 
	}

	friend const MatrixTriangular operator+(const MatrixTriangular& mattria1, const MatrixTriangular& mattria2) {
		return doAdd(mattria1, mattria2);
	}
	friend const MatrixTriangular operator-(const MatrixTriangular& mattria1, const MatrixTriangular& mattria2) {
		return doSubtract(mattria1, mattria2);
	}
	friend const MatrixTriangular operator*(const MatrixTriangular& matdiag1, const MatrixTriangular& matdiag2) {
		return doMultiply(matdiag1, matdiag2);
	}
	friend const MatrixTriangular operator*(const MatrixTriangular& matdiag, const ElementType& element) {
		return doElementMultiply(matdiag, element);
	}
	friend const MatrixTriangular operator*(const ElementType& element, const MatrixTriangular& matdiag) {
		return doElementMultiply(matdiag, element);
	}

private:
	bool PushValue(const ElementType& element);
	
	vector<vector<ElementType>> _mut; 
};

template<typename ElementType>
MatrixTriangular<ElementType>::MatrixTriangular(const MatrixBase& matbase)
	: MatrixBase(matbase) {
	enum matrix_type type;
	type = matbase.GetType();
	if (MATRIX_COMMON == type) {
		if (matbase.IsTriangularLower())
			type = MATRIX_TRIANGULAR_LOWER;
		else if (matbase.IsTriangularUpper())
			type = MATRIX_TRIANGULAR_UPPER;
		else {
			SetErrMsg("Can't find right type.");
			abort();
		}
	}
	else if (MATRIX_DIAGONAL == type) 
		type == MATRIX_TRIANGULAR_DEFAULT;

	_type = type;
	_mut.resize(GetRow());
	switch (_type) {
		case MATRIX_TRIANGULAR_LOWER:
			for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
				_mut[rIdx].resize(rIdx+1);
				for (size_t cIdx = 0; cIdx < GetCol(); cIdx++) {
					if (cIdx < rIdx+1)
						assert(SetValue(rIdx, cIdx, matbase.GetValue(rIdx, cIdx)));
					else
						assert(_c_zero == matbase.GetValue(rIdx, cIdx));
				}
			}
			break;
		case MATRIX_TRIANGULAR_UPPER:
			for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
				_mut[rIdx].resize(GetRow()-rIdx);
				for (size_t cIdx = 0; cIdx < GetCol(); cIdx++) {
					if (rIdx <= cIdx)
						assert(SetValue(rIdx, cIdx, matbase.GetValue(rIdx, cIdx)));
					else
						assert(_c_zero == matbase.GetValue(rIdx, cIdx));
				}
			}
			break;
		default:
			break;
	}
}

template<typename ElementType>
bool MatrixTriangular<ElementType>::PushValue(const ElementType& element) {
	switch (_type) {
		case MATRIX_TRIANGULAR_LOWER:
			if (_rowidx == GetRow()-1 && _colidx == GetCol()) {
				SetErrMsg("This mutrix is full, can't push");
				return false;
			}
			else {
				_mut[_rowidx][_colidx] = element;
				_colidx++;
				if (_rowidx < GetRow()-1 && _colidx == _rowidx+1) {
					_colidx = 0;
					_rowidx++;
				}
				return true;
			}
			break;
		case MATRIX_TRIANGULAR_UPPER:
			if (_rowidx == GetRow()-1 && _colidx == 1) {
				SetErrMsg("This mutrix is full, can't push");
				return false;
			}
			else {
				_mut[_rowidx][_colidx] = element;
				_colidx++;
				if (_rowidx < GetRow()-1 && _colidx == GetCol()) {
					_colidx = _rowidx + 1;
					_rowidx++;
				}
				return true;
			}
			break;
		default:
			break;
	}
	return false;
}

template<typename ElementType>
const ElementType& MatrixTriangular<ElementType>::GetValue(size_t rowidx, size_t colidx) const {
	assert(rowidx < GetRow());
	assert(colidx < GetCol());
	switch (_type) {
		case MATRIX_TRIANGULAR_LOWER:
			if (rowidx >= colidx)
				return _mut[rowidx][colidx];
			break;
		case MATRIX_TRIANGULAR_UPPER:
			if (rowidx <= colidx)
				return _mut[rowidx][colidx-rowidx];
			break;
		default:
			break;
	}
	return _c_zero;
}

template<typename ElementType>
bool MatrixTriangular<ElementType>::SetValue(size_t rowidx, size_t colidx, const ElementType& element) {
	assert(rowidx < GetRow());
	assert(colidx < GetCol());
	switch (_type) {
		case MATRIX_TRIANGULAR_LOWER:
			if (rowidx >= colidx) {
				_mut[rowidx][colidx] = element;
				return true;
			}
			break;
		case MATRIX_TRIANGULAR_UPPER:
			if (rowidx <= colidx) {
				_mut[rowidx][colidx-rowidx] = element;
				return true;
			}
			break;
		default:
			break;
	}
	if (element != _c_zero) {
		SetErrMsg("uncorrect row or col input");
		return false;
	}
	return true;
}

template<typename ElementType>
void MatrixTriangular<ElementType>::Print(void) const {
	cout << "[" << GetRow() << "," << GetCol() << "] "; 
	switch (_type) {
		case MATRIX_TRIANGULAR_LOWER:
			for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
				for (size_t cIdx = 0; cIdx < GetCol(); cIdx++) {
					cout << GetValue(rIdx, cIdx) << " ";
				}
			}
			break;
		case MATRIX_TRIANGULAR_UPPER:
			for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
				for (size_t cIdx = 0; cIdx < GetCol(); cIdx++) {
					cout << GetValue(rIdx, cIdx) << " ";
				}
			}
			break;
		default:
			break;
	}
	cout << endl;
}

template<typename ElementType>
MatrixTriangular<ElementType> MatrixTriangular<ElementType>::Transpose(void) const {
	enum matrix_type type;
	if (MATRIX_TRIANGULAR_LOWER == _type)
		type = MATRIX_TRIANGULAR_UPPER;
	else if (MATRIX_TRIANGULAR_UPPER == _type)
		type = MATRIX_TRIANGULAR_LOWER;
	MatrixTriangular<ElementType> matcomm(GetCol(), type);
	switch (type) {
		case MATRIX_TRIANGULAR_LOWER:
			for (size_t cIdx = 0; cIdx < GetCol(); cIdx++) {
				for (size_t rIdx = 0; rIdx < cIdx+1; rIdx++) {
					matcomm.SetValue(cIdx, rIdx, GetValue(rIdx, cIdx));
				}
			}
			break;
		case MATRIX_TRIANGULAR_UPPER:
			for (size_t cIdx = 0; cIdx < GetCol(); cIdx++) {
				for (size_t rIdx = cIdx; rIdx < GetRow(); rIdx++) {
					matcomm.SetValue(cIdx, rIdx, GetValue(rIdx, cIdx));
				}
			}
			break;
		default:
			break;
	}
	return matcomm;
}

template<typename ElementType>
MatrixTriangular<ElementType>& MatrixTriangular<ElementType>::operator=(const MatrixBase<ElementType>& matbase) {
	if (this != &matbase)
		MatrixBase::operator=(matbase);
	return (*this);
}

template<typename ElementType>
const MatrixTriangular<ElementType> doAdd(const MatrixTriangular<ElementType>& mattria1, 
										const MatrixTriangular<ElementType>& mattria2) {
	assert(mattria1.GetRow() == mattria2.GetRow());
	assert(mattria1.GetCol() == mattria2.GetCol());
	assert(mattria1.GetRow() == mattria1.GetCol());
	assert(mattria1.GetType() == mattria2.GetType());
	MatrixTriangular<ElementType> matsum(mattria1);
	ElementType element;
	for (size_t rIdx = 0; rIdx < matsum.GetRow(); rIdx++) {
		for (size_t cIdx = 0; cIdx < rIdx+1; cIdx++) {
			element = mattria1.GetValue(rIdx, cIdx) + mattria2.GetValue(rIdx, cIdx);
			matsum.SetValue(rIdx, cIdx, element);
		}
	}
	return matsum;
}

template<typename ElementType>
const MatrixTriangular<ElementType> doSubtract(const MatrixTriangular<ElementType>& mattria1, 
										const MatrixTriangular<ElementType>& mattria2) {
	assert(mattria1.GetRow() == mattria2.GetRow());
	assert(mattria1.GetCol() == mattria2.GetCol());
	assert(mattria1.GetRow() == mattria1.GetCol());
	assert(mattria1.GetType() == mattria2.GetType());
	MatrixTriangular<ElementType> matsum(mattria1);
	ElementType element;
	for (size_t rIdx = 0; rIdx < matsum.GetRow(); rIdx++) {
		for (size_t cIdx = 0; cIdx < rIdx+1; cIdx++) {
			element = mattria1.GetValue(rIdx, cIdx) - mattria2.GetValue(rIdx, cIdx);
			matsum.SetValue(rIdx, cIdx, element);
		}
	}
	return matsum;
}

template<typename ElementType>
const MatrixTriangular<ElementType> doMultiply(const MatrixTriangular<ElementType>& mattria1, 
									const MatrixTriangular<ElementType>& mattria2) {
	assert(mattria1.GetRow() == mattria2.GetCol());
	assert(mattria1.GetCol() == mattria2.GetRow());
	assert(mattria1.GetType() == mattria2.GetType());
	MatrixTriangular<ElementType> matproduct(mattria1.GetRow(), mattria1.GetType());
	ElementType element;
	for (size_t rIdx = 0; rIdx < mattria1.GetRow(); rIdx++) {
		for (size_t cIdx = 0; cIdx < mattria2.GetCol(); cIdx++) {
			element = 0;
			for (size_t tIdx = 0; tIdx < mattria1.GetCol(); tIdx++) 
				element += mattria1.GetValue(rIdx, tIdx) * mattria2.GetValue(tIdx, cIdx);
			assert(matproduct.SetValue(rIdx, cIdx, element));
		}
	}
	return matproduct;
}

template<typename ElementType>
const MatrixTriangular<ElementType> doElementMultiply(const MatrixTriangular<ElementType>& mattria, 
												  const ElementType& element) {
	MatrixTriangular<ElementType> matproduct(mattria, mattria.GetType());
	for (size_t rIdx = 0; rIdx < matproduct.GetRow(); rIdx++) {
		for (size_t cIdx = 0; cIdx < matproduct.GetCol(); cIdx++) {
			assert(matproduct.SetValue(rIdx, cIdx, matproduct.GetValue(rIdx, cIdx) * element));
		}
	}
	return matproduct;
}


#endif // SSY_MATRIX_TRIANGULAR_H


